﻿/*==========================================================【程式資訊及版權宣告】====
  SSCkeyboard
  Copyright(Pe) 2000-2011.
  update : 2.00
	* jquery
======================================================================================
程式用法
======================================================================================
  程式原作者: 天機–絕版危機(Canis TK)
    個人網站: http://www.clonefactor.com
    電子郵件: canistk@gmail.com
　MSN 帳號: canistk@yahoo.com
    ICQ 帳號: 22224880
    發表日期: 2003.09.14
======================================================================================

    版權所有
    請尊重智慧財產權： 無論您對本程式 SSCkeyboard 作任何修改請您*必須*保留此段版權宣告
    的內容，原作者的名字和網站連結。

    如果您想要以這個程式為基礎，製作以及公開發表您所修改過的版本，
    請您首先以傳送電子郵件的方式，徵求我們的同意。

    請不要將原作者的名字改成您自己的名字，然後以另一程式名稱重新命名後在網路上公開發表
    及散播本程式，因為這是嚴重的侵權行為。

    這是公益免費程式，所以請不要使用在商業用途上。
    另外，您亦不可將本程式(全部或部份)複製到其他儲存媒體(例如光碟片)上作販賣獲利用~。

    假如因為使用本程式而令您蒙受資料遺失或損毀，程式原作者均不用對其負責。

===以下為程式內碼===================================================================*/

(function($, window, document){
	var KEYSPLITBY = "|";
	//
	// Config
	//
	var config = {
		fps				: 500,		// 重覆執行的時間間隔
		key 			: [],		// 222 個鍵的初始化
		historylength	: 10,		// 按鍵紀錄長度
		history			: [],
		preventLevel	: 1			// [0|1|2] 阻止原有browser 功能鍵  0=not prevent, 1=non-text field prevent, 2=all
	};
	//
	// function ram
	//
	var ram = {
		_fnlist			: [],		// function list
		debug : function(_s){},		// null action
		init: function(){
			ram.reset();
			$(document).keydown(function(event){ ram._keydown(event); });
			$(document).keyup(function(event){ ram._keyup(event); });
			$(document).bind('cfkeydn', function(){ ram._keylistener() });
			$(document).bind('cfkeyup', function(){ ram._keylistener() });
		},
		bind : function(_obj){
			if( arguments.length>1 ){						// this is keybind in list.
				for(var i=0;i<arguments.length;i++){
					ram.bind( arguments[i] );
				}//for
				return true;
			}else{
				var _fnclass = new tools.fnClass();
				if( typeof _obj.key!=="undefined" && typeof _obj.fn!=="undefined" ){
					_fnclass.repeat = (typeof _obj.repeat!=="undefined")?_obj.repeat:_fnclass.repeat;
					_fnclass.order = (typeof _obj.order!=="undefined")?_obj.order:_fnclass.order;
					_fnclass.alone = (typeof _obj.alone!=="undefined")?_obj.alone:_fnclass.alone;
					_fnclass.fps = (typeof _obj.fps!=="undefined")?_obj.fps:config.fps;
					_fnclass.fn = (typeof _obj.fn!=="undefined")?_obj.fn:function(){};
					
					// keybind String > Array - hotfix
					if( typeof _obj.key=="string" ){ _obj.key = _obj.key.split(KEYSPLITBY); }//if
					
					var _fail=false;
					if( typeof _obj.key=="object" && _obj.key.length>0){
						for(var i=0;i<_obj.key.length;i++){
							if( ! tools.keyOnList(_obj.key[i])[0] ){
								_fail=true; return false;
							}else{
								_fnclass.key.push( tools.keyOnList(_obj.key[i])[1] );	// ready key set.
							}//if
						}//for
					}else{
						alert("keybind Error "+ _obj.key.join(KEYSPLITBY) );
						return false;
					}//if
					if (!_fail){
						ram._fnlist.push( _fnclass );	// apply to listener
						
						// keybind order - hotfix,
						if( _fnclass.order==true && config.historylength<_fnclass.key.length) config.historylength=_fnclass.key.length;
						
					}//if
					ram.debug("bind ["+ tools.keyCharArr(_fnclass.key) +"], repeat="+_fnclass.repeat+", order="+_fnclass.order );
				}//if
			}//if
		},// End bind
		unbind : function(_key){
			// keybind String > Array - hotfix
			if( typeof _key=="string" ){ _key = _key.split(KEYSPLITBY); }//if
			_key=_key.sort();
			var _fail=false;
			var _keyArr=[];
			if( typeof _key=="object" && _key.length>0){
				for(var i=0;i<_key.length;i++){
					if( ! tools.keyOnList(_key[i])[0] ){
						_fail=true; return false;
					}else{
						_keyArr.push( tools.keyOnList(_key[i])[1] );	// ready key set.
					}//if
				}//for
			}//if
			_keyArr=_keyArr.join(",").toLowerCase();
			for(i=ram._fnlist.length-1;i>0;i--){
				if( ram._fnlist[i].key.sort().join(",").toLowerCase() == _keyArr )
				ram._fnlist.splice(i,1); 	// delete this.
			}//for
		},// End unbind
		reset : function(){
			// reset history
			config.history = [];
			// reset function list
			ram._fnlist = [];
			// reset key
			config.key = new Array(223);
			for(var i=0; i<223; i++) config.key[i] = new tools.keyClass();
			config.key[8].name = "BackSpace";
			config.key[9].name = "Tab";
			config.key[12].name = "Center";
			config.key[13].name = "Enter";
			config.key[16].name = "Shift";	//***Function key
			config.key[17].name = "Ctrl";	//***Function key
			config.key[18].name = "Alt";	//***Function key
			config.key[19].name = "Pause";
			config.key[20].name = "Caps Lock";
			config.key[27].name = "Esc";
			config.key[32].name = "Space Bar";
			config.key[33].name = "Page Up";
			config.key[34].name = "Page Down";
			config.key[35].name = "End";
			config.key[36].name = "Home";
			config.key[37].name = "Arrow Left";
			config.key[38].name = "Arrow Up";
			config.key[39].name = "Arrow Right";
			config.key[40].name = "Arrow Down";
			config.key[44].name = "Print Screen"; //***Function key
			config.key[45].name = "Insert";
			config.key[46].name = "Delete";
			for (i=48; i<=57; i++) config.key[i].name = String.fromCharCode(i);	// a~z
			for (i=65; i<=90; i++) config.key[i].name = String.fromCharCode(i);	// A~Z
			config.key[91].name = "Start Menu(L)"; //***Function key
			config.key[92].name = "Start Menu(R)"; //***Function key
			config.key[93].name = "Menu"; //***Function key
			config.key[96].name = "Numpad 0";
			config.key[97].name = "Numpad 1";
			config.key[98].name = "Numpad 2";
			config.key[99].name = "Numpad 3";
			config.key[100].name = "Numpad 4";
			config.key[101].name = "Numpad 5";
			config.key[102].name = "Numpad 6";
			config.key[103].name = "Numpad 7";
			config.key[104].name = "Numpad 8";
			config.key[105].name = "Numpad 9";
			config.key[106].name = "Numpad *";
			config.key[107].name = "Numpad +";
			config.key[109].name = "Numpad -";
			config.key[110].name = "Numpad .";
			config.key[111].name = "Numpad /";
			for (i=112; i<=123; i++) config.key[i].name = "F"+(i-111);	// F1 ~ F12
			config.key[144].name = "Num Lock";
			config.key[145].name = "Scroll Lock";
			config.key[186].name = ";";	// ; 
			config.key[187].name = "=";	// =
			config.key[188].name = ",";	// ,
			config.key[189].name = "-";	// -
			config.key[190].name = ".";	// .
			config.key[191].name = "/";	// /
			config.key[192].name = "`";	// `
			config.key[219].name = "[";	// [
			config.key[220].name = "\\";	// \ 
			config.key[221].name = "]";	// ]
			config.key[222].name = "'";	// '
		},
		//
		// handle
		//
		_keydown : function(event){
			var _kt = tools.disableAllKeys(event);	// disable key function & return target type.
			ram.debug("key Down "+ config.key[event.keyCode].name+"["+event.keyCode+"]["+ _kt +"]" );
			var i;
			var e = event || window.event;
			var ekey = e.which ? e.which : e.keyCode;
			ekey = tools.keyFix(ekey);									// keyFix
			if(ekey >=8 && ekey <=222) {
				if (config.key[ekey].status == false){					// 鍵尚未按下
					config.key[ekey].status = true;						// 重設為按下
					if(config.historylength>0){							// log 記錄
						config.history.push(tools.keyAcsii(ekey));		// apply to last
						if( config.history.length > config.historylength )
							config.history.shift();						// delete the first one if that's too long
					} // End-if
					// ram.debug("hist :"+ config.history.join(',') );
					$(document).trigger('cfkeydn');						// only active event onPress, keyevent will fire repeaty when you hold-down the key.
				} // End-if
			} // End-if
		},//fn
		_keyup : function(event){
			var _kt = tools.disableAllKeys(event);	// disable key function & return target type.
			var i;
			var e = event || window.event;
			var ekey = e.which ? e.which : e.keyCode;
			ekey = tools.keyFix(ekey);									// keyFix
			if(ekey >=8 && ekey <=222) {
				if (config.key[ekey].status == true){					// 鍵尚未放開
					config.key[ekey].status = false;					// 重設
				}// End-if
			}// End-if
			ram.debug("key Up "+ config.key[event.keyCode].name+"["+event.keyCode+"]["+ _kt +"]" );
			$(document).trigger('cfkeyup');
		},//fn
		_keylistener : function(){
			// keep checking the key log & function list. to active those hotkey event
			if( ram._fnlist.length>0 ){
				var _fl=ram._fnlist;
				for(var i=0;i<_fl.length;i++){
					if( tools.allKeyPress(_fl[i]) ){
						if(!_fl[i]._status){
							if( _fl[i].alone ){	// this function need to run in alone mode.
								if( tools.keyAlone(_fl[i].key) ){
									ram._fnRun( _fl[i] , 0);
									_fl[i]._status=true;
								}//if
							}else{
								ram._fnRun( _fl[i] , 0);
								_fl[i]._status=true;
							}//if
						}//if
					}else{
						_fl[i]._status=false;
					}//if
				}//for
			}//if
		},//fn
		_fnRun : function(_class, _times){
			_times=(_times?_times:0);
			if( _class.repeat && _times>=0){
				// repeat by config.kps=0.05 sec
				if(_times>=0 && tools.allKeyPress(_class) ){					// first time
					_class.fn();							// repeat function
					_class._clock = setTimeout(function(){	// Queue to launch
						_times++;
						_class._status=true;
						ram._fnRun( _class, _times);
					}, _class.fps);
				}else{
					// key released,
					_class._status=false;
					clearTimeout(_class._clock);
				}//if
			}else if( !_class.repeat && !_class._status ){
				// do 1 times each press
				_class.fn();
				_class._status=true;
			}//if
		}
	}// ram
	//
	// Class
	//
	var tools = {
		keyClass : function(){
			this.name		=	"none";	// 鍵名
			this.status		=	false;	// 鍵狀態 down=true, up=false
			return this;
		},
		fnClass : function(){
			this.key		=	[];		// 組合鍵
			this.order		=	false;	// true=必須順序輸入, false=同時按下
			this.repeat		=	false;	// 啟動重複執行
			this.fps		=	-1;		// default by config.fps
			this.alone		=	false;	// 只能獨自按下時
			this._clock		=	0;		// fire event repeatly
			this._status	=	false;	// function running or not.
			this.fn			=	{};		// 寫入的執行指令
		},
		allKeyPress : function(_class){
			if( _class.order ){
				// by order, check keylog
				var _tag = _class.key.join(KEYSPLITBY);
				var _log = config.history.join(KEYSPLITBY);
				if( (_log.lastIndexOf(_tag)+_tag.length)==_log.length ){	// this is the last key log on the config.history
					// match
					ram.debug("S["+tools.keyCharArr(_class.key)+"]"+ (_log.lastIndexOf(_tag)==-1?"not match":"MATCH" ) +"\nL["+tools.keyCharArr(config.history)+"]"+" lastOrder="+  ( ( _log.lastIndexOf(_tag)+_tag.length )==_log.length)  );
					return true;
				}//if
			}else{
				// press together
				var _press=true, _arr=_class.key;
				for(var i=0;i<_arr.length;i++){
					if( !config.key[_arr[i]].status ) _press=false;	// THIS will not run, if all key._status==true
				}//for
				if( _press ){
					// match
					return true;
				}//if
			}//if
			return false;
		},
		keyAlone : function(_arr){
			var _fl=ram._fnlist.slice();						// copy a token of function list
			var _k1,_k2;
			for(var k=0;k<_arr.length;k++){								// each key in keyset
					for(var i=_fl.length-1;i>=0;i--){
						_k1= _fl[i].key;
						_k2= _arr[k];
						if( $.inArray(_k2,_k1)==-1 || _k1==_k2){						// remove all other fnClass not active && havn't then key.
								_fl.splice(i,1);							// remove this
						}//if
					}//for
			}//for
			if( _fl.length==0) return true;
			// the list lefted is the hotkey contain current key.
			// check on the other key if they all press or not.
			_k1=true;	// keep this to true
			for(i=_fl.length-1;i>=0;i--){
				_k1=_fl[i].key;
				for(k=0;k<_k1.length;k++){
					_k1 = (config.key[ _k1[k] ].status && _k1);	// turn to false if somekey not pressed.
				}//for
				if( _k1 ) return false;	// true, mean other hotkey was active. "keyAlone" will false
			}//for
			// ram.debug("keyAlone : ALL PASS");
			return true;
		},
		keyFix : function(_c){
			// 59 ";" 分號和冒號, IE = 186
			// 61 "=" firfox keycode, 187: "=" //ie keycode
			// 189 "-" in firfox keycode = 109 as same as "numpad =" can't fix....
			if($.browser.msie){
			}else if($.browser.chrome){
			}else if($.browser.mozilla){
				switch(_c){
					case 59: _c=186;	// ":"
				}//case
			}else if($.browser.safari){
				// no error found
			}else if($.browser.opera){
				switch(_c){
					case 57383: e=189;	break; // The "numpad -" also turn to "-" key.... mush select one.
					case 57384: e=187;	break; // "="
					case 57402: e=17;	break; // Ctrl Opera 7.11
					case 57356: e=123;	break; // F12 Opera 7.11
				}//case
			}//if
			return _c;
		},//End keyFix
		keyOnList : function(_char){
			var _pt=0;
			var _flag=false;
			if( typeof _char=="string" ){
				for(var i=8;i<223;i++){	// find match
					if(config.key[i].name.toUpperCase() == _char.toUpperCase()){ _flag=true; _pt=i; break;}
				}//for
			}//if
			return [_flag,_pt];
		},
		keyAcsii : function(_acsii){	// char to acsii
			if (_acsii>=0 && _acsii<=222 && typeof(_acsii)!="string") {
				return _acsii;
			}else{
				for (var i=0; i<=222; i++){
					if (_key[i].name.toLowerCase() == acsii.toLowerCase()){
						return i;
						break;
					} // End-if
				}// End-loop
			} // End-if
		},// End keyAcsii
		keyChar : function(_acsii){
			var _char;
			try{
				_char=config.key[_acsii].name;
			}catch(e){ _char="*error*" }
			return _char;
		},
		keyCharArr : function(_arr){
			var _hist=[];
			for(var i=0;i<_arr.length;i++){
				_hist.push( tools.keyChar(parseInt(_arr[i])) );
			}//for
			return _hist;
		},
		// 原有功能鍵禁止
		// 不能禁止 Print screen, Ctrl+Alt+del 系統層面熱鍵
		// 進入 text / textarea 時本功能關閉
		disableAllKeys : function(e){
			var _ee = (e.srcElement? e.srcElement : e.target);
			switch( config.preventLevel ){
				default:
				case 0:
					return "allow";		// not prevent
				break;
				case 2:
					tools.disableKey(e);
					return "prevent";		// prevent all keyevent
				break;
				case 1:
					try{
						if(_ee.type != "text" &&
							_ee.type !="textarea" &&
							_ee.type != "password" &&
							_ee.type != "button" &&
							_ee.type != "checkbox" ){
							tools.disableKey(e);
							return "prevent";
						} // End-if
					}catch(err){ return "error" }
					return "allow";
				break;
			}//case
		},// End-disableAllkeys
		disableKey : function(e){
			e.cancelBubble = true;	// ie
			e.returnValue = false;	// ie
			if(e.stopPropagation){
				e.stopPropagation();	// FF
				e.preventDefault();		// FF
			}//if
			return false;
		}// End-disableKey
	}// tools
	//
	// API
	//
	var _keyapi = {
		debug		: function(_fn){ if(typeof _fn=="function") ram.debug=_fn; else if(typeof _fn=="string") ram.debug(_fn); else ram.debug=function(_s){} },
		bind		: ram.bind,
		unbind		: ram.unbind,
		reset		: ram.reset,
		importList: function(_obj){ ram._fnlist = _obj;	},
		exportList : function(){ return ram._fnlist; },
		history : function(){ return tools.keyCharArr(config.history); }
	}// keyapi
	
	//
	// jquery redirection
	//
	$.extend({
		keyBind : _keyapi.bind,
		keyboard : _keyapi
	});
	
	
	//
	// Autoload.
	//
	try{
		$(document).ready(function(){ram.init();});
	}catch(e){
		ram.debug("keyboard Fail")
	}//try
})(jQuery, window, document);  // inject global jQuery object